package timing.ukulele.auth.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
import timing.ukulele.auth.config.properties.TimingSecurityProperties;
import timing.ukulele.auth.model.ScopeWithDescription;
import timing.ukulele.auth.persistent.OAuth2ClientScope;
import timing.ukulele.auth.service.OAuth2ClientScopeService;
import timing.ukulele.auth.vo.ConsentParameterVO;
import timing.ukulele.common.data.ResponseData;

import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.*;

/**
 * 认证服务器相关自定接口
 */
@RequestMapping("/oauth2")
@RestController
@Tag(name = "OAuth2客户端", description = "只能由开发人员操作")
@ApiResponses(value = {
        @ApiResponse(responseCode = "1000", description = "成功", content = {@Content(mediaType = "application/json")}),
        @ApiResponse(responseCode = "1001", description = "失败", content = {@Content(mediaType = "application/json")}),
        @ApiResponse(responseCode = "3000", description = "业务处理失败", content = {@Content(mediaType = "application/json")})
})
public class AuthorizationController {
    private final TimingSecurityProperties securityProperties;
    private final RegisteredClientRepository registeredClientRepository;
    private final OAuth2AuthorizationConsentService authorizationConsentService;
    private final OAuth2ClientScopeService scopeService;
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public AuthorizationController(
            TimingSecurityProperties securityProperties,
            RegisteredClientRepository registeredClientRepository,
            OAuth2AuthorizationConsentService authorizationConsentService,
            OAuth2ClientScopeService scopeService) {
        this.securityProperties = securityProperties;
        this.registeredClientRepository = registeredClientRepository;
        this.authorizationConsentService = authorizationConsentService;
        this.scopeService = scopeService;
    }

    @SneakyThrows
    @ResponseBody
    @GetMapping(value = "/consent/redirect")
    @Operation(summary = "授权确认重定向", description = "授权确认重定向地址与相关参数")
    public ResponseData<String> consentRedirect(HttpServletRequest request,
                                                HttpServletResponse response,
                                                @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
                                                @RequestParam(OAuth2ParameterNames.STATE) String state,
                                                @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
                                                @RequestParam(name = OAuth2ParameterNames.USER_CODE, required = false) String userCode) {

        // 携带当前请求参数重定向至前端页面
        UriComponentsBuilder uriBuilder = UriComponentsBuilder
                .fromUriString(securityProperties.getConsentPageUri())
                .queryParam(OAuth2ParameterNames.SCOPE, UriUtils.encode(scope, StandardCharsets.UTF_8))
                .queryParam(OAuth2ParameterNames.STATE, UriUtils.encode(state, StandardCharsets.UTF_8))
                .queryParam(OAuth2ParameterNames.CLIENT_ID, clientId)
                .queryParam(OAuth2ParameterNames.USER_CODE, userCode);

        String uriString = uriBuilder.build(Boolean.TRUE).toUriString();
        if (ObjectUtils.isEmpty(userCode) || !UrlUtils.isAbsoluteUrl(securityProperties.getDeviceActivateUri())) {
            // 不是设备码模式或者设备码验证页面不是前后端分离的，无需返回json，直接重定向
            redirectStrategy.sendRedirect(request, response, uriString);
            return null;
        }
        // 兼容设备码，需响应JSON，由前端进行跳转
        return ResponseData.success(uriString);
    }

    @ResponseBody
    @GetMapping(value = "/consent/parameters")
    @Operation(summary = "授权确认相关参数", description = "授权确认相关参数")
    @Parameters(value = {
            @Parameter(name = "client_id", description = "client_id", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "scope", description = "scope", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "state", description = "state", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "user_code", description = "user_code", in = ParameterIn.QUERY)
    })
    public ResponseData<ConsentParameterVO> consentParameters(Principal principal,
                                                              @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
                                                              @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
                                                              @RequestParam(OAuth2ParameterNames.STATE) String state,
                                                              @RequestParam(name = OAuth2ParameterNames.USER_CODE, required = false) String userCode) {

        if (principal == null) {
            throw new RuntimeException("认证信息已失效.");
        }
        Set<String> previouslyApprovedScopes = new HashSet<>();
        RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
        if (registeredClient == null) {
            throw new RuntimeException("客户端不存在");
        }
        OAuth2AuthorizationConsent currentAuthorizationConsent =
                this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
        Set<String> authorizedScopes;
        if (currentAuthorizationConsent != null) {
            authorizedScopes = currentAuthorizationConsent.getScopes();
        } else {
            authorizedScopes = Collections.emptySet();
        }
        for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
            if (OidcScopes.OPENID.equals(requestedScope)) {
                continue;
            }
            if (authorizedScopes.contains(requestedScope)) {
                previouslyApprovedScopes.add(requestedScope);
            }
        }
        List<OAuth2ClientScope> scopeList = this.scopeService.list();
        Set<ScopeWithDescription> scopeWithDescriptionSet = new HashSet<>();
        Set<ScopeWithDescription> approvedScopeWithDescriptionSet = new HashSet<>();
//        ScopeWithDescription scopeItem = new ScopeWithDescription(OidcScopes.PROFILE,
//                "个人信息");
//        scopeWithDescriptionSet.add(scopeItem);
        ScopeWithDescription scopeItem;
        if (!CollectionUtils.isEmpty(scopeList)) {
            for (OAuth2ClientScope item : scopeList) {
                scopeItem = new ScopeWithDescription(item.getName(), item.getDescription());
                scopeWithDescriptionSet.add(scopeItem);
                if (previouslyApprovedScopes.contains(item.getName())) {
                    approvedScopeWithDescriptionSet.add(scopeItem);
                }
            }
        }
        ConsentParameterVO parameters = new ConsentParameterVO();
        parameters.setClientId(registeredClient.getClientId());
        parameters.setClientName(registeredClient.getClientName());
        parameters.setState(state);
        parameters.setScopes(scopeWithDescriptionSet);
        parameters.setPreviouslyApprovedScopes(approvedScopeWithDescriptionSet);
        parameters.setPrincipalName(principal.getName());
        parameters.setUserCode(userCode);
        if (StringUtils.hasText(userCode)) {
            parameters.setRequestURI("/oauth2/device_verification");
        } else {
            parameters.setRequestURI("/oauth2/authorize");
        }
        return ResponseData.success(parameters);
    }
}
